package com.eesujie.catimor.util;

import com.eesujie.catimor.exception.ReflectionException;
import org.jetbrains.annotations.NotNull;

import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

public final class ReflectionUtils {
    private ReflectionUtils() {}

    public static boolean deepContainsAnnotations(@NotNull Class<?> targetClazz,
                                                  @NotNull Class<? extends Annotation> annotationClazz) {

        if (targetClazz.getAnnotation(annotationClazz) != null) return true;
        if (targetClazz.getPackageName().startsWith("java.lang.annotation")) return false;
        for (var annotation : targetClazz.getAnnotations()) {
            var type = annotation.annotationType();
            if (deepContainsAnnotations(type, annotationClazz)) return true;
        }

        return false;
    }
    public static boolean deepContainsAnnotations(@NotNull Field field,
                                                  @NotNull Class<? extends Annotation> annotationClazz) {

        if (field.getAnnotation(annotationClazz) != null) return true;
        for (var annotation : field.getAnnotations()) {
            var type = annotation.annotationType();
            if (deepContainsAnnotations(type, annotationClazz)) return true;
        }

        return false;
    }

    public static boolean deepContainsAnnotations(@NotNull Method method,
                                                  @NotNull Class<? extends Annotation> annotationClazz) {

        if (method.getAnnotation(annotationClazz) != null) return true;

        for (var annotation : method.getAnnotations()) {
            var type = annotation.annotationType();
            if (deepContainsAnnotations(type, annotationClazz)) return true;
        }

        return false;
    }

    public static boolean deepContainsAnnotations(@NotNull Constructor<?> constructor,
                                                  @NotNull Class<? extends Annotation> annotationClazz) {

        if (constructor.getAnnotation(annotationClazz) != null) return true;

        for (var annotation : constructor.getAnnotations()) {
            var type = annotation.annotationType();
            if (deepContainsAnnotations(type, annotationClazz)) return true;
        }

        return false;
    }

    @NotNull
    public static <T> T getInstance(Class<T> type, Object... args) {
        try {
            return getSuitableConstructor(type).newInstance(args);
        } catch (IllegalAccessException e) {
            throw new ReflectionException(
                    String.format("class: %s and its constructor should be accessible", type.getName()));
        } catch (Exception e) {
            throw new ReflectionException(e);
        }
    }

    @NotNull
    @SuppressWarnings("unchecked")
    public static <T> Constructor<T> getSuitableConstructor(Class<T> type) {
        try {
            var cs = type.getConstructors();
            if (cs.length == 0) {
                cs = type.getDeclaredConstructors();
            }
            if (cs.length != 1) {
                throw new ReflectionException(
                        String.format("More than one constructor found in class %s",
                                type.getName()));
            }
            return (Constructor<T>) cs[0];
        } catch (SecurityException e) {
            throw new ReflectionException(e);
        }
    }
}
